package restdom.validation;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.util.Assert;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

@Aspect
public class ValidationAspect implements InitializingBean, MessageSourceAware {

	private Validator[] validators;

	private Validator[] onCreateValidators;

	private Validator[] onUpdateValidators;

	private String objectName = "target";

	private boolean failFast = false;

	private MessageSource messageSource;

	private Class<?> supportedClass;

	protected Errors validate(Object object) {
		Errors errors = new BeanPropertyBindingResult(object, objectName);
		invokeValidators(validators, object, errors);
		return errors;
	}

	protected void invokeValidators(Validator[] validators, Object object,
			Errors errors) {
		if (validators == null) {
			return;
		}
		for (Validator validator : validators) {
			if (isFailFast() && errors.hasErrors()) {
				break;
			}
			if (!validator.supports(object.getClass())) {
				continue;
			}
			ValidationUtils.invokeValidator(validator, object, errors);
		}
	}

	@Before(value = "restdom.SystemArchitecture.createOperation() && args(object)", argNames = "object")
	public void validateOnCreate(Object object) throws ValidationErrors {
		if (!supports(object)) {
			return;
		}
		Errors errors = validate(object);
		invokeValidators(onCreateValidators, object, errors);
		if (errors.hasErrors()) {
			throw new ValidationErrors(errors, messageSource);
		}
	}

	@Before(value = "restdom.SystemArchitecture.updateOperation() && args(object)", argNames = "object")
	public void validateOnUpdate(Object object) throws ValidationErrors {
		if (!supports(object)) {
			return;
		}
		Errors errors = validate(object);
		invokeValidators(onUpdateValidators, object, errors);
		if (errors.hasErrors()) {
			throw new ValidationErrors(errors, messageSource);
		}
	}

	protected boolean supports(Object object) {
		return supports(object.getClass());
	}

	protected boolean supports(Class<?> clazz) {
		if (this.supportedClass == null) {
			return true;
		}
		return this.supportedClass.isAssignableFrom(clazz);
	}

	public boolean isFailFast() {
		return failFast;
	}

	public void setFailFast(boolean failFast) {
		this.failFast = failFast;
	}

	public void setOnCreateValidators(Validator[] onCreateValidators) {
		this.onCreateValidators = onCreateValidators;
	}

	public void setOnUpdateValidators(Validator[] onUpdateValidators) {
		this.onUpdateValidators = onUpdateValidators;
	}

	public void setValidators(Validator[] validators) {
		this.validators = validators;
	}

	public void setObjectName(String objectName) {
		this.objectName = objectName;
	}

	@Required
	public void setMessageSource(MessageSource messageSource) {
		this.messageSource = messageSource;
	}

	public void setSupportedClass(Class clazz) {
		this.supportedClass = clazz;
	}

	public void afterPropertiesSet() throws Exception {
		int configuredValidators = 0;
		configuredValidators += validators != null ? validators.length : 0;
		configuredValidators += onCreateValidators != null ? onCreateValidators.length
				: 0;
		configuredValidators += onUpdateValidators != null ? onUpdateValidators.length
				: 0;
		Assert.isTrue(configuredValidators > 0, "You must configure at least"
				+ " one validator using the 'validator', 'onCreateValidators'"
				+ " or 'onUpdateValidators' properties");
	}
}
